#include "stdafx.h"
#include "mysql_connection.h"
#include "mysql_recordset.h"
#include "type.h"
#include "mysql_module.h"

namespace SMysqlSpace {
	// charset array
	static const char* gMySQLCharsets[] = {
		"big5",
		"dec8",
		"cp850",
		"hp8",
		"koi8r",
		"latin1",
		"latin2",
		"swe7",
		"ascii",
		"ujis",
		"sjis",
		"hebrew",
		"tis620",
		"euckr",
		"koi8u",
		"gb2312",
		"greek",
		"cp1250",
		"gbk",
		"latin5",
		"armscii8",
		"utf8",
		"ucs2",
		"cp866",
		"keybcs2",
		"macce",
		"macroman",
		"cp852",
		"latin7",
		"cp1251",
		"cp1256",
		"cp1257",
		"binary",
		"geostd8",
		"cp932",
		"eucjpms",
		"utf8mb4",
	};

	CMysqlConnection::CMysqlConnection() : m_mySQL(nullptr), m_terminate(false) {
		memset(&m_connectInfo, 0, sizeof(m_connectInfo));
	}

	CMysqlConnection::~CMysqlConnection() {
	}

	bool CMysqlConnection::Connect(const char* ip, uint16 port,
		const char* user, const char* password, const char* db,
		const char* charset
	) {
		if (nullptr == user || nullptr == password) {
			Critical("[mysql] szName or szPassword is nullptr in [%s]", __FUNCTION__);
			return false;
		}

		if (nullptr == ip || ip[0] == '\0') {
			Info("[mysql] set invalid ip, so set default one");
			strSafeNCopy(m_connectInfo.ip, "localhost");
		}

		strSafeNCopy(m_connectInfo.ip, ip);
		m_connectInfo.wPort = port;
		strSafeNCopy(m_connectInfo.user, user);
		strSafeNCopy(m_connectInfo.password, password);
		strSafeNCopy(m_connectInfo.db, db);
		strSafeNCopy(m_connectInfo.charset, charset);
		return connect();
	}

	bool CMysqlConnection::Connect(const SConnectMysqlInfo& stConnectInfo) {
		m_connectInfo = stConnectInfo;
		return connect();
	}

	static bool charsetCheck(const char* charset) {
		for (int i = 0; i < ArraySize(gMySQLCharsets); i++) {
			if (strcmp(gMySQLCharsets[i], charset) == 0) {
				return true;
			}
		}
		return false;
	}

	bool CMysqlConnection::connect() {
		if (nullptr == m_mySQL) {
			m_mySQL = mysql_init(nullptr);
		}

		if (nullptr == m_mySQL) {
			Critical("[mysql] init mysql error;");
			return false;
		}

		if (!charsetCheck(m_connectInfo.charset)) {
			Critical("char set init error");
			return false;
		}

		// set mysql charset
		if (mysql_options(m_mySQL, MYSQL_SET_CHARSET_NAME, m_connectInfo.charset)) {
			Critical("set mysql character set error");
			return false;
		}

		// mysql real connect.
		if (!mysql_real_connect(m_mySQL,
			m_connectInfo.ip,
			m_connectInfo.user,
			m_connectInfo.password,
			m_connectInfo.db,
			m_connectInfo.wPort,
			nullptr, 0)
		) {
			Critical("[mysql] connect mysql error");
			return false;
		}

		// set mysql reconnect flat.
		char reconnect = 1;
		mysql_options(m_mySQL, MYSQL_OPT_RECONNECT, &reconnect);

		return true;
	}

	bool CMysqlConnection::Reconnect() {
		this->Close();
		return connect();
	}

	int STDCALL CMysqlConnection::QueryWithResultSync(const char* sql, CMysqlRecordset* poRes) {
		if (nullptr == sql || nullptr == m_mySQL || nullptr == poRes) {
			return MYSQL_EXECUTE_PARA_ERROR;
		}

		uint32 sqlLen = (uint32)strlen(sql);
		if (mysql_real_query(m_mySQL, sql, sqlLen)) {
			uint32 dwError = mysql_errno(m_mySQL);

			// log out error
			Critical("[mysql] mysql_real_query error: %u", dwError);

			if (CR_SERVER_LOST == dwError ||
				CR_CONN_HOST_ERROR == dwError ||
				CR_SERVER_GONE_ERROR == dwError) {
				if (!Reconnect()) {
					return MYSQL_CONNECT_ERROR;
				}

				if (0 != mysql_real_query(m_mySQL, sql, sqlLen)) {
					Critical("[mysql] mysql_real_query error");
					return MYSQL_EXECUTE_UNKNOWN;
				}
			} else {
				Critical("[mysql] mysql_real_query error");
				return MYSQL_EXECUTE_RESULT_FAIL;
			}
		}

		// fetch result, then return
		MYSQL_RES* pResult = mysql_store_result(m_mySQL);
		if (nullptr == pResult) {
			Critical("[mysql] store result error");
			return MYSQL_EXECUTE_RESULT_FAIL;
		}
		poRes->AttachRowRes(pResult);

		// return result numbers
		if (mysql_num_rows(pResult) > 0) {
			return MYSQL_EXECUTE_RESULT_WITH_RES;
		} else {
			return MYSQL_EXECUTE_RESULT_WITHOUT_RES;
		}
	}

	int CMysqlConnection::QueryWithResult(const char* sql, IMysqlRecordset** ppoRecordSet) {
		if (nullptr == sql || nullptr == m_mySQL) {
			return MYSQL_EXECUTE_PARA_ERROR;
		}

		uint32 sqlLen = (uint32)strlen(sql);
		if (mysql_real_query(m_mySQL, sql, sqlLen)) {
			auto error = mysql_errno(m_mySQL);

			// log out error
			Critical("[mysql] mysql_real_query error: %u", error);

			if (CR_SERVER_LOST == error ||
				CR_CONN_HOST_ERROR == error ||
				CR_SERVER_GONE_ERROR == error) {
				if (!Reconnect()) {
					return MYSQL_CONNECT_ERROR;
				}

				if (mysql_real_query(m_mySQL, sql, sqlLen)) {
					return MYSQL_EXECUTE_UNKNOWN;
				}
			}
			else {
				return MYSQL_EXECUTE_RESULT_FAIL;
			}
		}

		// fetch result, then return
		MYSQL_RES* pResult = mysql_store_result(m_mySQL);
		if (nullptr == pResult) {
			Critical("[mysql] store result error");
			return MYSQL_EXECUTE_RESULT_FAIL;
		}

		CMysqlRecordset* poRSet = CMysqlModule::Instance()->CreateRecordset();
		if (nullptr == poRSet) {
			Critical("[mysql] get CMysqlRecordset error");
			return MYSQL_EXECUTE_RESULT_FAIL;
		}

		new (poRSet) CMysqlRecordset(pResult);
		*ppoRecordSet = poRSet;
		if (nullptr == ppoRecordSet) {
			return MYSQL_EXECUTE_RESULT_FAIL;
		}

		// return result numbers
		if (mysql_num_rows(pResult) > 0) {
			return MYSQL_EXECUTE_RESULT_WITH_RES;
		} else {
			return MYSQL_EXECUTE_RESULT_WITHOUT_RES;
		}
	}

	int CMysqlConnection::QueryWithoutResult(const char* sql) {
		if (nullptr == sql || nullptr == m_mySQL) {
			return MYSQL_EXECUTE_PARA_ERROR;
		}

		uint32 dwSqlLen = (uint32)strlen(sql);
		if (mysql_real_query(m_mySQL, sql, dwSqlLen)) {
			auto error = mysql_errno(m_mySQL);

			// log out error
			Critical("[mysql] mysql_real_query error: %u", error);

			// try to reconnect mysql if it is closed.
			if (CR_SERVER_LOST == error ||
				CR_CONN_HOST_ERROR == error ||
				CR_SERVER_GONE_ERROR == error) {
				if (!Reconnect()) {
					return MYSQL_CONNECT_ERROR;
				}

				if (mysql_real_query(m_mySQL, sql, dwSqlLen)) {
					return MYSQL_EXECUTE_UNKNOWN;
				}
			} else {
				return MYSQL_EXECUTE_RESULT_FAIL;
			}
		}

		// release results;
		mysql_free_result(mysql_store_result(m_mySQL));

		// return effect rows;
		return (int)mysql_affected_rows(m_mySQL);
	}

	void CMysqlConnection::Close() {
		if (m_mySQL != nullptr) {
			mysql_close(m_mySQL);
			m_mySQL = nullptr;
		}
	}

	int CMysqlConnection::EscapeString(const char* from, int fromLen,
		char* to, int toLen
	) {
		if (nullptr == m_mySQL || nullptr == from) {
			return -1;
		}

		// length check
		if (toLen < fromLen * 2 + 1) {
			Critical("[mysql] to buffer length is too less");
			return -1;
		}

		unsigned long len = mysql_real_escape_string(m_mySQL, to, from, fromLen);
		if (len > (uint32)toLen) {
			Critical("[mysql] escape data error");
			return -1;
		}
		else {
			return (int)len;
		}
	}

	// Trigger the thread to execute all command, 
	// then call doRelease() function to release all resources.
	void CMysqlConnection::Release() {
		m_commandQueue.PushBack(nullptr);
	}

	void CMysqlConnection::doRelease() {
		this->stop();
		this->Close();
		this->exit();

		delete this;
	}

	void CMysqlConnection::run() {
		for (; !m_terminate; ) {
			auto res = m_commandQueue.GetFront();
			if (!res.first) {
				// no result, just have a rest.
            #if defined(WIN32) || defined(WIN64)
				sleep(1);
            #else
				sleep(1000);
            #endif
			}
			else {
				// has result
				if (nullptr == res.second) {
					// exit flag.
					this->doRelease();
					return;
				} else {
					// user command, just return to main thread.
					auto poCommand = res.second;
					poCommand->OnExecuteSql(this);
					CMysqlModule::Instance()->AddExecutedCommand(poCommand);
				}
			}
		}
	}

	void CMysqlConnection::AddCommand(ICommand* poCommand, bool priority) {
		if (nullptr == poCommand) {
			return;
		}
		if (!priority) {
			m_commandQueue.PushBack(poCommand);
		} else {
			m_commandQueue.PushFront(poCommand);
		}
	}

	void CMysqlConnection::stop() {
		m_terminate = true;
	}

	const char* CMysqlConnection::GetLastError() {
		return mysql_error(m_mySQL);
	}

	int CMysqlConnection::GetLastErrorNo() {
		return mysql_errno(m_mySQL);
	}
}